package com.thz.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.thz.common.core.constants.Constants;
import com.thz.common.core.enums.ResultCode;
import com.thz.common.core.result.Result;
import com.thz.exception.ApplicationException;
import com.thz.system.mapper.exam.ExamMapper;
import com.thz.system.mapper.exam.ExamQuestionMapper;
import com.thz.system.mapper.question.QuestionMapper;
import com.thz.system.mapper.user.UserExamMapper;
import com.thz.system.mapper.user.UserMapper;
import com.thz.system.mapper.user.UserSubmitMapper;
import com.thz.system.pojo.exam.ExamQuestion;
import com.thz.system.pojo.exam.Exam;
import com.thz.system.pojo.exam.dto.ExamAddDTO;
import com.thz.system.pojo.exam.dto.ExamEditDTO;
import com.thz.system.pojo.exam.dto.ExamQueryDTO;
import com.thz.system.pojo.exam.dto.ExamQuestAddDTO;
import com.thz.system.pojo.exam.vo.ExamDetailVO;
import com.thz.system.pojo.exam.vo.ExamVO;
import com.thz.system.pojo.homework.dto.HomeworkAddDTO;
import com.thz.system.pojo.homework.dto.HomeworkQueryDTO;
import com.thz.system.pojo.homework.vo.HomeworkQuestionVO;
import com.thz.system.pojo.homework.vo.HomeworkScoreVO;
import com.thz.system.pojo.homework.dto.HomeworkUserDTO;
import com.thz.system.pojo.question.Question;
import com.thz.system.pojo.question.vo.QuestionVO;
import com.thz.system.pojo.user.User;
import com.thz.system.pojo.user.UserExam;
import com.thz.system.pojo.user.UserSubmit;
import com.thz.system.service.exam.IExamService;
import com.thz.system.manager.ExamCacheManager;
import com.thz.system.utlis.ExamUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @Description
 * @Author thz
 * @Date 2025/3/24
 */
@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        // 开启分页
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    @Override
    public List<ExamVO> listHomework(HomeworkQueryDTO homeworkQueryDTO) {
        PageHelper.startPage(homeworkQueryDTO.getPageNum(), homeworkQueryDTO.getPageSize());
        return examMapper.selectExamListHomework(homeworkQueryDTO);
    }

    @Override
    public String add(ExamAddDTO examAddDTO) {
        checkExamSaveParams(examAddDTO,null);
        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO,exam);
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }

    @Override
    public String addHomework(HomeworkAddDTO homeworkAddDTO) {
        checkHomeworkSaveParams(homeworkAddDTO,null);
        Exam exam = new Exam();
        BeanUtil.copyProperties(homeworkAddDTO,exam);
        exam.setTypes(1);
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }

    @Override
    public Boolean questionAdd(ExamQuestAddDTO examQuestAddDTO) {
        // 1.校验参数
        Exam exam = getExam(examQuestAddDTO.getExamId());
        checkExam(exam);
        // 2.查询题目列表
        Set<Long> questionIdSet = examQuestAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }

        // 3.查询题目列表并批量提交
        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {
            throw new ApplicationException(Result.failed(ResultCode.FAILED_NOT_EXISTS));
        }
        return saveExamQuestion(exam, questionIdSet);
    }

    @Override
    public int questionDelete(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        if(Constants.TRUE.equals(exam.getStatus())){
            throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_PUBLISH));
        }
        checkExam(exam);
        return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
    }

    @Override
    public ExamDetailVO detail(Long examId) {
        ExamDetailVO examDetailVO = new ExamDetailVO();
        Exam exam = getExam(examId);
        BeanUtil.copyProperties(exam, examDetailVO);
        List<QuestionVO> questionVOList = examQuestionMapper.selectExamQuestionList(examId);
        if (CollectionUtil.isEmpty(questionVOList)) {
            return examDetailVO;
        }
        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }

    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        if(Constants.TRUE.equals(exam.getStatus())){
            throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_PUBLISH));
        }
        checkExam(exam);
        checkExamSaveParams(examEditDTO,exam.getExamId());
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    @Override
    public int editHomework(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteHomework(Long examId) {
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        return examMapper.deleteById(examId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delete(Long examId) {
        Exam exam = getExam(examId);
        if(Constants.TRUE.equals(exam.getStatus())){
            throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_PUBLISH));
        }
        checkExam(exam);
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        return examMapper.deleteById(examId);
    }

    @Override
    public int publish(Long examId) {
        Exam exam = getExam(examId);
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_FINISH));
        }
        // 判断竞赛是否包含题目
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if (count == null || count <= 0) {
            throw new ApplicationException(Result.failed(ResultCode.EXAM_NOT_HAS_QUESTION));
        }
        exam.setStatus(Constants.TRUE);
        // 将竞赛信息存入redis中
        examCacheManager.addCache (exam);
        return examMapper.updateById(exam);
    }

    @Override
    public int publishHomework(Long examId) {
        Exam exam = getExam(examId);
        // 判断作业是否包含题目
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if (count == null || count <= 0) {
            throw new ApplicationException(Result.failed(ResultCode.HOMEWORK_NOT_HAS_QUESTION));
        }
        exam.setStatus(Constants.TRUE);
        // 将竞赛信息存入redis中
        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublish(Long examId) {
        Exam exam = getExam(examId);
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_FINISH));
        }
        checkExam(exam);
        exam.setStatus(Constants.FALSE);
        // 将竞赛信息存入 redis 中
        examCacheManager.deleteCache(examId);
        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublishHomework(Long examId) {
        Exam exam = getExam(examId);
        exam.setStatus(Constants.FALSE);
        // 将竞赛信息存入 redis 中
        examCacheManager.deleteCache(examId);
        return examMapper.updateById(exam);
    }

    private void checkHomeworkSaveParams(HomeworkAddDTO homeworkAddDTO, Long examId) {
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                        .eq(Exam::getTitle, homeworkAddDTO.getTitle())
                        .eq(Exam::getTypes, 1)
                        .eq(Exam::getCourseId, homeworkAddDTO.getCourseId())
                        .ne(examId != null, Exam::getExamId, examId));
        if (CollectionUtil.isNotEmpty(examList)) {
            throw new ApplicationException(Result.failed(ResultCode.FAILED_ALREADY_EXISTS));
        }
        ExamUtils.checkHomeworkSaveParams(homeworkAddDTO.getStartTime(), homeworkAddDTO.getEndTime());
    }

    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .eq(Exam::getTypes, Constants.TYPES)
                .ne(examId != null, Exam::getExamId, examId));
        if (CollectionUtil.isNotEmpty(examList)) {
            throw new ApplicationException(Result.failed(ResultCode.FAILED_ALREADY_EXISTS));
        }
        ExamUtils.checkExamSaveParams(examSaveDTO.getStartTime(), examSaveDTO.getEndTime());
    }

    private void checkExam(Exam exam) {
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ApplicationException(Result.failed(ResultCode.EXAM_STARTED));
        }
    }

    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ApplicationException(Result.failed(ResultCode.EXAM_NOT_EXISTS));
        }
        return exam;
    }

    private boolean saveExamQuestion(Exam exam, Set<Long> questionIdSet) {
        int num = 1;
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            ExamQuestion checkQuestionId = examQuestionMapper.selectOne(new LambdaQueryWrapper<ExamQuestion>()
                    .select(ExamQuestion::getQuestionId)
                    .eq(ExamQuestion::getQuestionId,questionId)
                    .eq(ExamQuestion::getExamId,exam.getExamId()));
            if (checkQuestionId != null) {
                throw new ApplicationException(Result.failed(ResultCode.EXAM_IS_REPEAT));
            }
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);
            examQuestionList.add(examQuestion);
        }
        // 批量插入
        return saveBatch(examQuestionList);
    }


    @Override
    public List<HomeworkQuestionVO> getHomeworkQuestionVO(Long examId) {
        List<ExamQuestion> checkQuestionId = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId,examId));
        List<HomeworkQuestionVO> homeworkQuestionVOList = new ArrayList<>();

        for (ExamQuestion examQuestion : checkQuestionId) {
            HomeworkQuestionVO homeworkQuestionVO = new HomeworkQuestionVO();
            Question question = questionMapper.selectById(examQuestion.getQuestionId());
            homeworkQuestionVO.setQuestionId(question.getQuestionId());
            homeworkQuestionVO.setDifficulty(question.getDifficulty());
            homeworkQuestionVO.setTitle(question.getTitle());
            homeworkQuestionVOList.add(homeworkQuestionVO);
        }

        return homeworkQuestionVOList;
    }

    @Override
    public List<HomeworkUserDTO> getHomeworkUserDTO(Long questionId,Long courseId,Long examId) {
        List<UserSubmit> checkQuestionId = userSubmitMapper.selectList(new LambdaQueryWrapper<UserSubmit>()
                .select(UserSubmit::getUserId,UserSubmit::getPass,UserSubmit::getProgramType,UserSubmit::getUserCode)
                .eq(UserSubmit::getQuestionId,questionId).eq(UserSubmit::getCourseId,courseId).eq(UserSubmit::getExamId,examId));
        List<HomeworkUserDTO> homeworkUserDTOList = new ArrayList<>();

        for (UserSubmit userSubmit : checkQuestionId) {
            HomeworkUserDTO homeworkUserDTO = new HomeworkUserDTO();
            Question question = questionMapper.selectById(questionId);
            homeworkUserDTO.setHomeworkName(question.getTitle());
            User user = userMapper.selectById(userSubmit.getUserId());
            homeworkUserDTO.setUserName(user.getNickName());
            homeworkUserDTO.setUserCode(userSubmit.getUserCode());
            homeworkUserDTO.setProgramType(userSubmit.getProgramType());
            homeworkUserDTO.setPass(userSubmit.getPass());
            homeworkUserDTOList.add(homeworkUserDTO);
        }
        return homeworkUserDTOList;
    }

    @Override
    public List<HomeworkScoreVO> getHomeworkScore(Long examId, Long courseId) {
        List<UserExam> userExamList = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .select(UserExam::getUserId)
                .eq(UserExam::getExamId, examId));
        List<HomeworkScoreVO> homeworkScoreVOList = new ArrayList<>();

        for (UserExam userExam : userExamList) {
            HomeworkScoreVO homeworkScoreVO = new HomeworkScoreVO();
            homeworkScoreVO.setTotalScore(getTotalScore(examId));
            homeworkScoreVO.setScore(getScore(examId,courseId,userExam.getUserId()));
            User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                    .select(User::getNickName)
                    .eq(User::getUserId, userExam.getUserId()));
            homeworkScoreVO.setStudentName(user.getNickName());
            homeworkScoreVOList.add(homeworkScoreVO);
        }
        return homeworkScoreVOList;
    }

    private Integer getTotalScore(Long examId){
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId,examId));
        List<Integer> scoreList = new ArrayList<>();
        for (ExamQuestion examQuestion : examQuestionList) {
            Question question = questionMapper.selectOne(new LambdaQueryWrapper<Question>()
                    .select(Question::getDifficulty).eq(Question::getQuestionId,examQuestion.getQuestionId()));
            scoreList.add(question.getDifficulty()*100);
        }
        Integer score = 0;
        for (Integer score1 : scoreList) {
            score += score1;
        }
        return score;
    }


    private Integer getScore(Long examId,Long courseId,Long userId){
        List<UserSubmit> userSubmits = userSubmitMapper.selectList(new LambdaQueryWrapper<UserSubmit>()
                .select(UserSubmit::getScore)
                .eq(UserSubmit::getCourseId,courseId)
                .eq(UserSubmit::getCreateUser,userId)
                .eq(UserSubmit::getExamId,examId)
                .orderByDesc(UserSubmit::getCreateTime));

        Integer score = 0;

        for (UserSubmit userSubmit : userSubmits) {
            score += userSubmit.getScore();
        }
        return score;
    }


}
